package max.ta.stock.plot;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import max.ta.data.Function;
import max.ta.data.entity.StockQuote;
import max.ta.data.plot.AbstractChartPlotter;
import max.ta.data.plot.TimerSeriesConverter;
import max.ta.data.plot.data.MathLine;
import max.ta.data.plot.data.MathPoint;
import max.ta.data.plot.data.MathSurface;
import max.ta.data.plot.gen.TimeSerieDataGenerator;
import max.ta.math.SeriesMath;
import max.ta.stock.indicator.IndicatorValue;
import max.ta.stock.plot.data.StockPointSetBuilder;
import max.ta.stock.plot.data.StockSurface;

import org.apache.log4j.Logger;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.DefaultHighLowDataset;
import org.jfree.ui.RefineryUtilities;

public abstract class AbstractFinancialChartPlotter
	extends AbstractChartPlotter<Date, Double>
	implements FinancialChartPlotter {

    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(FinancialChartPlotter.class);
    // ----------------------------------------------------------------------------------------
    // Axis
    // ----------------------------------------------------------------------------------------
    private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd-MM");
    private static final DateAxis DOMAIN_AXIS = new DateAxis("Date");
    private static final NumberAxis RANGE_AXIS = new NumberAxis("Price");
    // --------------------------------------------------------------------------------
    // Renderer
    // --------------------------------------------------------------------------------
    private static final boolean RESET_RANGE = true;
    private static final double SCALE = 0.02;
    // -----------------------------------------------------------------------------
    private final StockPointSetBuilder builder = new StockPointSetBuilder();

    public AbstractFinancialChartPlotter(String title) {
	super(title, DOMAIN_AXIS, RANGE_AXIS);
	DOMAIN_AXIS.setDateFormatOverride(FORMATTER);
	DOMAIN_AXIS.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void plotSimple(String symbol, List<StockQuote> quotes) {
	plotOverlaid(symbol, quotes, Collections.EMPTY_LIST);
    }

    @Override
    public void plotOverlaid(String symbol, List<StockQuote> quotes, List<IndicatorValue> values) {
	builder.buildSeries(quotes);
	StockSurface stockData = builder.getStockData();
	logger.info("Plotting : " + stockData);
	DefaultHighLowDataset dataset1 = createDataset(stockData);
	final JFreeChart chart = createBaseChart(dataset1);
	final XYPlot plot = chart.getXYPlot();
	if (!values.isEmpty()) {
	    MathLine<Date, Double> points = convert("MA", values);
	    MathSurface<Date, Double> series = new MathSurface<Date, Double>();
	    series.add(points);
	    final TimeSeriesCollection dataset2 = TimerSeriesConverter.createDataset(series);
	    plot.setDataset(1, dataset2);
	    plot.setRenderer(1, new StandardXYItemRenderer());
	}
	if (RESET_RANGE) {
	    resetRange(stockData);
	}
	showWindow(chart);
    }

    private void showWindow(final JFreeChart chart) {
	// Frame
	final ChartPanel chartPanel = createChartPanel(chart);
	setContentPane(chartPanel);
	pack();
	RefineryUtilities.centerFrameOnScreen(this);
	setVisible(VISIBLE);
    }

    // -----------------------------------------------------------------------------------------------------
    @Override
    public void plot(String serieName, Function<Date, Double> fx, Date minX, Date maxX) {
	final TimeSerieDataGenerator generator = new TimeSerieDataGenerator(fx, minX, maxX);
	plot(serieName, generator.generate());
    }

    @Override
    protected JFreeChart createChart(MathSurface<Date, Double> points) {
	// TODO Auto-generated method stub
	throw new UnsupportedOperationException();
    }

    // -----------------------------------------------------------------------------------------------------
    protected abstract JFreeChart createBaseChart(DefaultHighLowDataset dataset);

    private void resetRange(StockSurface data) {
	double min = SeriesMath.min(getSerieValues(data.getLow()));
	double max = SeriesMath.max(getSerieValues(data.getHigh()));
	double low = (1 - SCALE) * min;
	double high = (1 + SCALE) * max;
	RANGE_AXIS.setRange(low, high);
    }

    private DefaultHighLowDataset createDataset(StockSurface data) {
	final Date[] dates = getDatesValues(data.getOpen());
	final double[] open = getSerieValues(data.getOpen());
	final double[] low = getSerieValues(data.getLow());
	final double[] high = getSerieValues(data.getHigh());
	final double[] close = getSerieValues(data.getClose());
	final double[] volume = getSerieValues(data.getVolume());
	DefaultHighLowDataset dataSet = new DefaultHighLowDataset("Daily OHLC", dates, high, low, open, close, volume);
	return dataSet;
    }

    private Date[] getDatesValues(MathLine<Date, Double> points) {
	int n = points.getSize();
	Date[] dates = new Date[n];
	for (int i = 0; i < n; i++) {
	    dates[i] = points.getElement(i).getX();
	}
	return dates;
    }

    private double[] getSerieValues(MathLine<Date, Double> points) {
	int n = points.getSize();
	double[] values = new double[n];
	for (int i = 0; i < n; i++) {
	    values[i] = points.getElement(i).getY();
	}
	return values;
    }

    private MathLine<Date, Double> convert(String serieName, List<IndicatorValue> values) {
	MathLine<Date, Double> points = new MathLine<Date, Double>(serieName);
	for (IndicatorValue v : values) {
	    MathPoint<Date, Double> p = new MathPoint<Date, Double>(v.getDate(), v.getValue());
	    points.add(p);
	}
	return points;
    }
}
